use std::process::Stdio;
use std::collections::{HashMap, HashSet};
use tauri::State;
use crate::core::AppState;
use once_cell::sync::Lazy;
use std::sync::Mutex;
use portable_pty::{CommandBuilder, PtySize, native_pty_system, MasterPty};
use uuid::Uuid;
use serde::Serialize;
use std::io::Read;
use std::thread;
// use std::path::Path;
// use std::borrow::Cow;

#[tauri::command]
pub async fn run_command(_state: State<'_, AppState>, cmd: String, cwd: Option<String>) -> Result<(i32, String, String), String> {
	let command_line = cmd.trim().to_string();
	if command_line.is_empty() { return Err("empty command".into()); }

	let is_windows = cfg!(target_os = "windows");
	let cwd_opt = cwd.clone();
	let cmd_owned = command_line.clone();

    let output_res = tokio::task::spawn_blocking(move || {
		let mut c = std::process::Command::new(if is_windows { "cmd" } else { "sh" });
		if is_windows { c.arg("/C").arg(&cmd_owned); } else { c.arg("-lc").arg(&cmd_owned); }
		if let Some(dir) = cwd_opt { if !dir.is_empty() { c.current_dir(dir); } }
		c.stdout(Stdio::piped()).stderr(Stdio::piped());
		match c.output() {
			Ok(out) => {
				let code = out.status.code().unwrap_or(-1);
				let stdout = String::from_utf8_lossy(&out.stdout).into_owned();
				let stderr = String::from_utf8_lossy(&out.stderr).into_owned();
				Ok((code, stdout, stderr))
			},
			Err(e) => Err(e.to_string()),
		}
    }).await.map_err(|e| e.to_string())?;

    let output = output_res?;

	Ok(output)
}

static PTY_MAP: Lazy<Mutex<HashMap<String, Box<dyn portable_pty::Child + Send>>>> = Lazy::new(|| Mutex::new(HashMap::new()));

#[tauri::command]
pub async fn pty_spawn(
    app: tauri::AppHandle,
    _state: State<'_, AppState>,
    cwd: Option<String>,
    cols: u16,
    rows: u16,
    program: Option<String>,
    args: Option<Vec<String>>,
) -> Result<(String, String), String> {
    let pty_system = native_pty_system();
    let pair = pty_system.openpty(PtySize { rows, cols, pixel_width: 0, pixel_height: 0 }).map_err(|e| e.to_string())?;
    let mut cmd = if let Some(prog) = program.clone() { CommandBuilder::new(prog) } else {
        if cfg!(target_os = "windows") { CommandBuilder::new("cmd.exe") } else { CommandBuilder::new("/bin/bash") }
    };
    if let Some(vec_args) = args.clone() {
        for a in vec_args { cmd.arg(a); }
    }
    if let Some(dir) = cwd { if !dir.is_empty() { cmd.cwd(dir); } }
    let child = pair.slave.spawn_command(cmd).map_err(|e| e.to_string())?;
    let id = Uuid::new_v4().to_string();
    PTY_MAP.lock().unwrap().insert(id.clone(), child);
    let master: Box<dyn MasterPty + Send> = pair.master;
    let reader = master.try_clone_reader().map_err(|e| e.to_string())?;
    let writer = master.take_writer().map_err(|e| e.to_string())?;
    // stash master and io ends into a global map as well
    PTY_MASTER_MAP.lock().unwrap().insert(id.clone(), (master, writer));
    // initialize buffer and spawn background reader thread
    PTY_BUF_MAP.lock().unwrap().insert(id.clone(), Vec::new());
    {
        let id_clone = id.clone();
        let mut rdr = reader;
        let app_handle = app.clone();
        use tauri::Emitter;
        thread::spawn(move || {
            let mut local = [0u8; 8192];
            loop {
                let n = match rdr.read(&mut local) { Ok(n) => n, Err(_) => 0 };
                if n == 0 { break; }
                // push into buffer for pull fallback
                {
                    let mut map = PTY_BUF_MAP.lock().unwrap();
                    let entry = map.entry(id_clone.clone()).or_insert_with(Vec::new);
                    entry.extend_from_slice(&local[..n]);
                }
                // also emit event for streaming consumers
                let payload = String::from_utf8_lossy(&local[..n]).into_owned();
                let _ = app_handle.emit(&format!("pty:data:{}", id_clone), payload);
            }
        });
    }
    Ok((id, String::from("spawned")))
}

type PtyWriter = Box<dyn std::io::Write + Send>;
static PTY_MASTER_MAP: Lazy<Mutex<HashMap<String, (Box<dyn MasterPty + Send>, PtyWriter)>>> = Lazy::new(|| Mutex::new(HashMap::new()));
static PTY_BUF_MAP: Lazy<Mutex<HashMap<String, Vec<u8>>>> = Lazy::new(|| Mutex::new(HashMap::new()));

fn decode_command_output(bytes: &[u8]) -> String {
    // 1) BOM-aware UTF-16
    if bytes.len() >= 2 {
        let b0 = bytes[0];
        let b1 = bytes[1];
        if b0 == 0xFF && b1 == 0xFE {
            let (s, _, _) = encoding_rs::UTF_16LE.decode(&bytes[2..]);
            return s.into_owned();
        }
        if b0 == 0xFE && b1 == 0xFF {
            let (s, _, _) = encoding_rs::UTF_16BE.decode(&bytes[2..]);
            return s.into_owned();
        }
    }
    // 2) Heuristic UTF-16 (many zeros on either even or odd positions)
    if bytes.len() > 4 {
        let even_zeros = bytes.iter().step_by(2).filter(|&&b| b == 0).count();
        let odd_zeros = bytes.iter().skip(1).step_by(2).filter(|&&b| b == 0).count();
        let threshold = bytes.len() / 4; // 25%
        if odd_zeros > threshold || even_zeros > threshold {
            if odd_zeros >= even_zeros {
                let (s, _, _) = encoding_rs::UTF_16LE.decode(bytes);
                return s.into_owned();
            } else {
                let (s, _, _) = encoding_rs::UTF_16BE.decode(bytes);
                return s.into_owned();
            }
        }
    }
    // 3) UTF-8
    if let Ok(s) = String::from_utf8(bytes.to_vec()) { return s; }
    // 4) chardetng detection fallback
    let mut det = chardetng::EncodingDetector::new();
    det.feed(bytes, true);
    let enc = det.guess(None, true);
    let (cow, _, _) = enc.decode(bytes);
    cow.into_owned()
}

#[tauri::command]
pub async fn pty_resize(_state: State<'_, AppState>, id: String, cols: u16, rows: u16) -> Result<(), String> {
    if let Some((master, _w)) = PTY_MASTER_MAP.lock().unwrap().get_mut(&id) {
        master.resize(PtySize { cols, rows, pixel_width: 0, pixel_height: 0 }).map_err(|e| e.to_string())?;
        Ok(())
    } else {
        Err("pty not found".into())
    }
}

#[tauri::command]
pub async fn pty_write(_state: State<'_, AppState>, id: String, data: String) -> Result<(), String> {
    if let Some((_m, w)) = PTY_MASTER_MAP.lock().unwrap().get_mut(&id) {
        use std::io::Write;
        w.write_all(data.as_bytes()).map_err(|e| e.to_string())?;
        w.flush().map_err(|e| e.to_string())?;
        Ok(())
    } else {
        Err("pty not found".into())
    }
}

#[tauri::command]
pub async fn pty_read(_state: State<'_, AppState>, id: String) -> Result<String, String> {
    let mut map = PTY_BUF_MAP.lock().unwrap();
    if let Some(buf) = map.get_mut(&id) {
        if buf.is_empty() { return Ok(String::new()); }
        let out = String::from_utf8_lossy(&buf[..]).into_owned();
        buf.clear();
        Ok(out)
    } else { Err("pty not found".into()) }
}

#[tauri::command]
pub async fn pty_kill(_state: State<'_, AppState>, id: String) -> Result<(), String> {
    if let Some(mut child) = PTY_MAP.lock().unwrap().remove(&id) {
        let _ = child.kill();
    }
    PTY_MASTER_MAP.lock().unwrap().remove(&id);
    PTY_BUF_MAP.lock().unwrap().remove(&id);
    Ok(())
}

#[derive(Serialize, Clone)]
pub struct ShellSpec {
    pub id: String,
    pub name: String,
    pub program: String,
    pub args: Vec<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub kind: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub distro: Option<String>,
}

#[tauri::command]
pub async fn list_shells() -> Result<Vec<ShellSpec>, String> {
    let mut shells: Vec<ShellSpec> = Vec::new();
    let mut seen: HashSet<String> = HashSet::new();
    let mut seen_kinds: HashSet<String> = HashSet::new();
    #[cfg(target_os = "windows")]
    {
        // Git for Windows detection via Registry
        #[cfg(windows)]
        fn git_bash_registry_paths_inner() -> Vec<String> {
            let mut out = Vec::new();
            use winreg::enums::*;
            use winreg::RegKey;
            let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
            let hkcu = RegKey::predef(HKEY_CURRENT_USER);
            let keys = [
                (hklm.open_subkey("SOFTWARE\\GitForWindows"), "HKLM\\SOFTWARE\\GitForWindows"),
                (hklm.open_subkey("SOFTWARE\\WOW6432Node\\GitForWindows"), "HKLM\\SOFTWARE\\WOW6432Node\\GitForWindows"),
                (hkcu.open_subkey("SOFTWARE\\GitForWindows"), "HKCU\\SOFTWARE\\GitForWindows"),
            ];
            for (res, _name) in keys {
                if let Ok(key) = res {
                    if let Ok(path) = key.get_value::<String, _>("InstallPath") {
                        let norm = path.replace('/', "\\");
                        out.push(format!("{}\\bin\\bash.exe", norm));
                        out.push(format!("{}\\usr\\bin\\bash.exe", norm));
                    }
                }
            }
            out
        }
        #[cfg(not(windows))]
        fn git_bash_registry_paths_inner() -> Vec<String> { Vec::new() }
        // discover via PATH
        let win_candidates = vec![
            ("cmd", "cmd"),
            ("powershell", "powershell"),
            ("pwsh", "pwsh"),
            ("wsl", "wsl"),
            ("git-bash", "bash"),
        ];
        let mut found_wsl = false;
        for (idn, prog_name) in win_candidates {
            if let Ok(p) = which::which(prog_name) {
                let pstr = p.to_string_lossy().into_owned();
                let pl = pstr.to_lowercase();
                let (name, args) = match idn {
                    "cmd" => ("Command Prompt".to_string(), vec![]),
                    "powershell" => ("Windows PowerShell".to_string(), vec![]),
                    "pwsh" => ("PowerShell".to_string(), vec![]),
                    "git-bash" => {
                        if pl.contains("git\\") || pl.contains("git/") {
                            ("Git Bash".to_string(), vec!["--login".into(), "-i".into()])
                        } else {
                            // non-Git bash on Windows is confusing (legacy WSL bash.exe);
                            // skip to avoid duplicate with WSL entry
                            continue;
                        }
                    },
                    "wsl" => { found_wsl = true; ("WSL".to_string(), vec![]) },
                    _ => (idn.to_string(), vec![]),
                };
                let key = format!("{}|{}", pstr.to_lowercase(), args.join(" ").to_lowercase());
                if seen.insert(key) {
                    if idn == "git-bash" {
                        if !seen_kinds.insert("git-bash".to_string()) { continue; }
                    }
                    shells.push(ShellSpec { id: idn.to_string(), name, program: pstr, args, kind: Some(idn.to_string()), distro: None });
                }
            }
        }
        // enumerate WSL distros if wsl exists
        if found_wsl {
            if let Ok(out) = std::process::Command::new("wsl").arg("-l").arg("-q").output() {
                if out.status.success() {
                    let text = decode_command_output(&out.stdout);
                    for line in text.lines() {
                        let d = line.trim();
                        if d.is_empty() { continue; }
                        let prog = "C:/Windows/System32/wsl.exe".to_string();
                        let args: Vec<String> = vec!["-d".into(), d.into()];
                        let key = format!("{}|{}", prog.to_lowercase(), format!("-d {}", d).to_lowercase());
                        if seen.insert(key) {
                            shells.push(ShellSpec { id: format!("wsl:{}", d), name: format!("WSL: {}", d), program: prog.clone(), args, kind: Some("wsl".into()), distro: Some(d.into()) });
                        }
                    }
                }
            }
        }
        // Registry Git Bash paths
        for p in git_bash_registry_paths_inner() {
            let path = std::path::Path::new(&p);
            if path.exists() {
                let prog = path.to_string_lossy().into_owned();
                let args: Vec<String> = vec!["--login".into(), "-i".into()];
                let key = format!("{}|{}", prog.to_lowercase(), args.join(" ").to_lowercase());
                if seen.insert(key) {
                    if !seen_kinds.insert("git-bash".to_string()) { continue; }
                    shells.push(ShellSpec { id: "git-bash".into(), name: "Git Bash".into(), program: prog, args, kind: Some("git-bash".into()), distro: None });
                }
            }
        }
        // absolute fallbacks
        let abs = vec![
            ("Git Bash", "C:/Program Files/Git/bin/bash.exe"),
            ("Git Bash", "C:/Program Files/Git/usr/bin/bash.exe"),
            ("WSL", "C:/Windows/System32/wsl.exe"),
        ];
        for (name, p) in abs {
            let path = std::path::Path::new(p);
            if path.exists() {
                let prog = path.to_string_lossy().into_owned();
                let args: Vec<String> = if name=="Git Bash" { vec!["--login".into(), "-i".into()] } else { vec![] };
                let key = format!("{}|{}", prog.to_lowercase(), args.join(" ").to_lowercase());
                if seen.insert(key) {
                    if name=="WSL" && found_wsl { continue; }
                    if name=="Git Bash" && !seen_kinds.insert("git-bash".to_string()) { continue; }
                    shells.push(ShellSpec { id: name.to_lowercase().replace(' ', "-"), name: name.to_string(), program: prog, args, kind: Some(name.to_lowercase().replace(' ', "-")), distro: None });
                }
            }
        }
    }
        #[cfg(not(target_os = "windows"))]
    {
        let cands: Vec<(&str, &str)> = vec![
            ("bash", "/bin/bash"),
            ("zsh", "/bin/zsh"),
            ("fish", "/bin/fish"),
            ("sh", "/bin/sh"),
        ];
        for (name, path) in cands {
            if std::path::Path::new(path).exists() {
                shells.push(ShellSpec { id: name.to_string(), name: name.to_uppercase(), program: path.to_string(), args: vec!["-l".into()], kind: Some(name.to_string()), distro: None });
            }
        }
    }
    if shells.is_empty() {
        // Fallback
        #[cfg(target_os = "windows")]
        shells.push(ShellSpec { id: "cmd".into(), name: "Command Prompt".into(), program: "cmd.exe".into(), args: vec![], kind: Some("cmd".into()), distro: None });
        #[cfg(not(target_os = "windows"))]
        shells.push(ShellSpec { id: "sh".into(), name: "SH".into(), program: "/bin/sh".into(), args: vec![], kind: Some("sh".into()), distro: None });
    }
    Ok(shells)
}


